//
// Created by Administrator on 2021/10/21.
//

#include "BufferGeometry.h"

// 绘制模式 Draw Mode
const static std::map<int, const char*> dm_items = {
        std::make_pair(GL_POINTS, "GL_POINTS"),
        std::make_pair(GL_LINES, "GL_LINES"),
        std::make_pair(GL_LINE_LOOP, "GL_LINE_LOOP"),
        std::make_pair(GL_LINE_STRIP, "GL_LINE_STRIP"),
        std::make_pair(GL_TRIANGLES, "GL_TRIANGLES"),
        std::make_pair(GL_TRIANGLE_STRIP, "GL_TRIANGLE_STRIP"),
        std::make_pair(GL_TRIANGLE_FAN, "GL_TRIANGLE_FAN"),
};

// 多边形显示模式 Polygon Mode
const std::map<int, const char*> pm_items = {
        std::make_pair(GL_FILL, "GL_FILL"),
        std::make_pair(GL_LINE, "GL_LINE"),
        std::make_pair(GL_POINT, "GL_POINT"),
        std::make_pair(GL_RED, "GL_RED"),
        std::make_pair(GL_GREEN, "GL_GREEN"),
        std::make_pair(GL_BLUE, "GL_BLUE"),
        std::make_pair(GL_BLUE, "GL_ALPHA"),
        std::make_pair(GL_BLUE, "GL_RGB"),
        std::make_pair(GL_BLUE, "GL_RGBA"),
};

static int ct = 0;

BufferGeometry::BufferGeometry() {
    id = ++ct;
    uuid = String::toString("%p", this);
    type = GET_CLASS(__FILE__);
    name = type + "_" + id;

    this->bDrawIndices = false;
    this->drawCount = 0;

    // 默认 fill
    this->polygonMode = POLYGON_MODE;
    // 默认三角面
    this->drawMode = DRAW_MODE;
    this->p_material = NULL;
}

BufferGeometry::~BufferGeometry() {
    log_info("gc BufferGeometry.%p", this);
    // 释放资源
    if(!shaderAttribBufferMap.empty()) {
        for (auto iter = shaderAttribBufferMap.begin(); iter != shaderAttribBufferMap.end(); iter++) {
            if (iter != shaderAttribBufferMap.end()) {
                auto second = iter->second;
                if (second) {
                    // 释放指针
                    SAFE_DELETE(second);
                }
            }
        }
        std::map<GLuint , ShaderAttribBuffer *>().swap(shaderAttribBufferMap);
    }

    std::vector<float>().swap(vertices);
    std::vector<float>().swap(colors);
    std::vector<float>().swap(uvs);
    std::vector<float>().swap(normals);

    std::vector<int>().swap(indices);
    //SAFE_DELETE(p_material); Object3D提供的指针，由Object3D释放
}

/**
 * 应用材质
 * @param p_material 材质
 */
void BufferGeometry::setMaterial(Material *p_material) {
    SAFE_DELETE(this->p_material);
    this->p_material = p_material;
}

/**
 * 初始化
 */
void BufferGeometry::onInit() {
    log_info("BufferGeometry onInit. %p", this);

    if(!vertices.empty()){
        setUpAttribute(ShaderAttribute(DEFAULT_ATTRIB_NAME_VERTEX, GL_FLOAT, 3), vertices);

        if(!colors.empty()){
            setUpAttribute(ShaderAttribute(DEFAULT_ATTRIB_NAME_COLOR, GL_FLOAT, 4), colors);
        }

        if(!uvs.empty()){
            setUpAttribute(ShaderAttribute(DEFAULT_ATTRIB_NAME_UV, GL_FLOAT, 2), uvs);
        }

        if(!normals.empty()){
            setUpAttribute(ShaderAttribute(DEFAULT_ATTRIB_NAME_NORMAL, GL_FLOAT, 3), normals);
        }
    }
}


/**
 * 给attribute赋值
 * @param attrib attri属性
 * @param data 数组值
 */
void BufferGeometry::setUpAttribute(const ShaderAttribute &attrib, std::vector<float> data) {
    MAssert::check(p_material);
    Shader *p_shader = p_material->p_shader;
    if(p_shader->check()){
        GLint location = glGetAttribLocation( p_shader->getProgram(), attrib.attribName.c_str());
        if(location >= 0){
            setUpAttributeBuffer(location, attrib, data);
        }else {
            log_warn("未从shader:%s中获取到%s的变量地址", p_shader->getName().c_str(), attrib.attribName.c_str());
        }
    }else {
        log_warn("shader指针对象不存在%p", p_shader);
    }
}

/**
 * 创建VAO,VBO,EBO
 * @param location attribute 的 location
 * @param attrib 属性
 * @param data 数据
 */
void BufferGeometry::setUpAttributeBuffer(GLuint location, const ShaderAttribute &attrib, std::vector<float> data) {
    ShaderAttribBuffer * buffer = new ShaderAttribBuffer(attrib.attribName, location, attrib.numComponent, attrib.dataType, attrib.normalize, data);

    if(DEFAULT_ATTRIB_NAME_VERTEX == attrib.attribName){
        if(!indices.empty()){
            bDrawIndices = true;
            drawCount = indices.size();
        }else{
            drawCount = data.size() / attrib.numComponent;
        }
    }

    if(DEFAULT_ATTRIB_NAME_COLOR == attrib.attribName){
        p_material->useVertexColor = true;
    }

    //缓冲区对象
    GLuint VAO;
    GLuint VBO;
    GLuint EBO;

    glGenVertexArrays(1, &VAO);
    //1. 绑定顶点数组对象
    glBindVertexArray(VAO);
    glGenBuffers(1, &VBO);
    // 2. 把我们的顶点数组复制到一个顶点缓冲中，供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data[0]) * data.size(), &data[0], GL_STATIC_DRAW);

    if(VAO > 0 && VBO > 0) {
        buffer->VAO = VAO;
        buffer->VBO = VBO;

        if(!indices.empty()){
            glGenBuffers(1, &EBO);
            // 3. 复制我们的索引数组到一个索引缓冲中，供OpenGL使用
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), &indices[0], GL_STATIC_DRAW);
            buffer->EBO = EBO;
        }

        std::map<GLuint , ShaderAttribBuffer *>::iterator iter = shaderAttribBufferMap.find(buffer->location);
        if(iter != shaderAttribBufferMap.end()){

            GLuint bufferArr[3] = {
                    iter->second->VAO, iter->second->VBO, iter->second->EBO
            };
            glDeleteBuffers(1, bufferArr);
            SAFE_DELETE(iter->second);
            shaderAttribBufferMap.erase(buffer->location);
        }

        shaderAttribBufferMap.insert(std::make_pair(buffer->location, buffer));
    }
}


/**
 * 渲染
 * @param elapsedTime 时间
 */
void BufferGeometry::onRender(float elapsedTime, Matrix4x4 proj, Matrix4x4 view, Matrix4x4 pworld) {
    if(MAssert::isNull(p_material)) return;
    Shader *shader = p_material->p_shader;
    if(MAssert::isNull(shader)) return;
    if(MAssert::isNull(shader) || !shader->check() || drawCount <= 0) return;

    // use program
    glUseProgram(shader->getProgram());

    // bind vbo/ebo
    for (auto it = shaderAttribBufferMap.begin(); it != shaderAttribBufferMap.end(); ++it) {
        ShaderAttribBuffer * buffer = it->second;
        glBindBuffer(GL_ARRAY_BUFFER, buffer->VBO);

        glEnableVertexAttribArray(buffer->location);
        glVertexAttribPointer(buffer->location, buffer->numComponent, buffer->dataType, buffer->normalize, 0, (void *) 0);
    }

    // set uniform
    shader->setUniform("u_mvp", pworld * view * proj);
    shader->setUniform("use_vertex_color", p_material->useVertexColor);
    shader->setUniform("base_color", p_material->baseColor);
    shader->setUniform("u_pointSize", p_material->pointSize);

    // setting GL_FRONT_AND_BACK
    glPolygonMode(GL_FRONT_AND_BACK, polygonMode);
    if(GL_POINTS == drawMode){
        //点阵模式绘制
        glPointSize(p_material->pointSize);
    }

    // draw
    GLint location = glGetAttribLocation( shader->getProgram(), DEFAULT_ATTRIB_NAME_VERTEX.c_str());
    auto iter = shaderAttribBufferMap.find(location);
    if(drawCount > 0 && iter != shaderAttribBufferMap.end()){
        if(bDrawIndices) {
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iter->second->EBO);
            glDrawElements(drawMode, drawCount, GL_UNSIGNED_INT, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        } else {
            glBindBuffer(GL_ARRAY_BUFFER, iter->second->VBO);
            glDrawArrays(drawMode, 0 , drawCount);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
        }
    }
}

/**
 * 绘制GUI
 */
void BufferGeometry::drawUI() {
    {
        const char* dm_combo_preview_value;
        auto it = dm_items.find(drawMode);
        if(it != dm_items.end()){
            dm_combo_preview_value = it->second;
        }

        if (ImGui::BeginCombo(String::toString("Draw Model_%s", name.c_str()).c_str(), dm_combo_preview_value))
        {
            for (auto iterator = dm_items.begin(); iterator != dm_items.end(); ++iterator) {
                const bool is_selected = (drawMode == iterator->first);
                if (ImGui::Selectable(iterator->second, is_selected))
                    drawMode = iterator->first;
                if (is_selected)
                    ImGui::SetItemDefaultFocus();
            }
            ImGui::EndCombo();
        }
    }
    {
        const char* pm_combo_preview_value;
        auto it = pm_items.find(polygonMode);
        if(it != pm_items.end()){
            pm_combo_preview_value = it->second;
        }

        if (ImGui::BeginCombo(String::toString("Polygon Model_%s", name.c_str()).c_str(), pm_combo_preview_value))
        {
            for (auto iterator = pm_items.begin(); iterator != pm_items.end(); ++iterator) {
                const bool is_selected = (polygonMode == iterator->first);
                if (ImGui::Selectable(iterator->second, is_selected))
                    polygonMode = iterator->first;
                if (is_selected)
                    ImGui::SetItemDefaultFocus();
            }
            ImGui::EndCombo();
        }
    }
    ImGui::Text("Vertex Size: %d", vertices.size());
    ImGui::Text("bDraw Indices: %s", bDrawIndices ? "true" : "false");
    ImGui::Text("Draw Count: %d", drawCount);
    ImGui::Text("Indices: ");
    ImGui::SameLine();
    vectorToImGuiText(indices, 12);

    ImGui::Text("AttributeBuffer:");
    ImGui::SameLine();
    HelpMarker("Using TableSetupColumn() to alter resizing policy on a per-column basis.\n\n"
            "When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch.");
    {
        static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable;
        if (ImGui::BeginTable("table", 9, flags))
        {
            ImGui::TableSetupColumn("Location", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_DefaultSort);
            ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("NumComponent", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("DataType", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("Normalize", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("VAO", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("VBO", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("EBO", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("data", ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableHeadersRow();

            for (auto it = shaderAttribBufferMap.begin(); it != shaderAttribBufferMap.end(); ++it) {
                ImGui::TableNextRow();

                ShaderAttribBuffer * attribBuffer = it->second;

                // 数据类型解析为字符
                const char *dataTypeParse = "";
                switch (attribBuffer->dataType) {
                    case GL_FLOAT:
                        dataTypeParse = "GL_FLOAT";
                        break;
                    case GL_BYTE:
                        dataTypeParse = "GL_BYTE";
                        break;
                    case GL_UNSIGNED_BYTE:
                        dataTypeParse = "GL_UNSIGNED_BYTE";
                        break;
                    case GL_SHORT:
                        dataTypeParse = "GL_SHORT";
                        break;
                    case GL_UNSIGNED_SHORT:
                        dataTypeParse = "GL_UNSIGNED_SHORT";
                        break;
                    case GL_INT:
                        dataTypeParse = "GL_INT";
                        break;
                    case GL_UNSIGNED_INT:
                        dataTypeParse = "GL_UNSIGNED_INT";
                        break;
                }

                ImGui::TableSetColumnIndex(0);
                ImGui::Text("%d", attribBuffer->location);
                ImGui::TableSetColumnIndex(1);
                ImGui::Text("%s", attribBuffer->attribName.c_str());
                ImGui::TableSetColumnIndex(2);
                ImGui::Text("%d", attribBuffer->numComponent);
                ImGui::TableSetColumnIndex(3);
                ImGui::Text("%s", dataTypeParse);
                ImGui::TableSetColumnIndex(4);
                ImGui::Text("%s", attribBuffer->normalize ? "true" : "false");
                ImGui::TableSetColumnIndex(5);
                ImGui::Text("%d", attribBuffer->VAO);
                ImGui::TableSetColumnIndex(6);
                ImGui::Text("%d", attribBuffer->VBO);
                ImGui::TableSetColumnIndex(7);
                ImGui::Text("%d", attribBuffer->EBO);
                ImGui::TableSetColumnIndex(8);
                std::vector<float> data = attribBuffer->data;
                int numComPonent = attribBuffer->numComponent;
                vectorToImGuiText(data, numComPonent);
            }

            ImGui::EndTable();
        }
    }
}

















